// Copyright (c) Symbian Ltd 2008. All rights reserved.

// INCLUDE FILES

#include "BluetoothConnector.h"
#include "BluetoothServiceSearcher.h"
#include "BluetoothSockConnector.h"
#include "BluetoothSocketWriterReader.h"
#include "common.hrh"

const TInt KMaxIterConnection = 20;

/*
============================================================================
CBluetoothConnector's two stage constructor
============================================================================
*/
CBluetoothConnector* CBluetoothConnector::NewL(MBluetoothObserver& aConnObs, RSocketServ& aSocketServer, const TBTDevAddr& aDevAddr)
	{
    CBluetoothConnector* self = new (ELeave) CBluetoothConnector(aConnObs, aSocketServer, aDevAddr);
    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop();
    return self;
	}

/*
============================================================================
CBluetoothConnector's second phase constructor 
============================================================================
*/
void CBluetoothConnector::ConstructL()
	{	
	iServiceSearcher = new(ELeave) CBluetoothServiceSearcher(*this);
	iSocketConnector = CBluetoothSockConnector::NewL(iBtSocket, *this, iSocketServer);
	iBtSocketReader  = new(ELeave) CSocketReader(iBtSocket, *this);
	iBtSocketWriter  = new(ELeave) CSocketWriter(iBtSocket, *this);
	}

/*
============================================================================
CBluetoothConnector's constructor 
============================================================================
*/
CBluetoothConnector::CBluetoothConnector(MBluetoothObserver& aConnObs, RSocketServ& aSocketServer, const TBTDevAddr& aDevAddr):
 CBluetoothConnectionBase(ETrue), iConnObs(aConnObs), iSocketServer(aSocketServer), iBtDevAddr(aDevAddr),
 iState(EIdle), iPort(KPort), iMaxIterConnection(0)
	{
	}

/*
============================================================================
CBluetoothConnector's destructor
============================================================================
*/
CBluetoothConnector::~CBluetoothConnector()
	{
	delete iServiceSearcher;
	CancelSocket();
	iBtSocket.Close();
	delete iSocketConnector;
	delete iBtSocketReader;
	delete iBtSocketWriter;	
	}

/*
============================================================================
DoCancel is called as part of the active object's Cancel().
Cancels all outstanding BT socket operations
============================================================================
*/
void CBluetoothConnector::DoCancel()
	{
	CancelSocket();
	}

/*
============================================================================
This is the entry point of the CBluetoothConnector's state machine. The initial state must be EIdle. Different states
then EIdle will result in a KErrInUse. This is to prevent calling StartL() more than once if the state machine is already active.
============================================================================
*/
void CBluetoothConnector::StartL()
	{
	iConnected = EFalse;
	if (iState != EIdle)
		{
		User::Leave(KErrInUse);
		}
	else
		{
		SelfComplete();
		}
	}

/*
============================================================================
Handles CBluetoothConnector's state machine completion events
============================================================================
*/
void CBluetoothConnector::RunL()
	{
    switch (iState)
        {
        case EIdle:
        	//State machine entry point. Start searching for a service on the remote devices
        	SearchServiceL();
        	break;
        case ESearchingService:
        	//Searching for a service was successful. Start connecting to the remote device using the specific remote device
        	//service's port
        	ConnectDeviceL();
            break;   
        case EConnecting:
        	//The Master is succesfully connected to the Slave.
        	//The state machine can start listening for incoming data. Data transfer can begin
        case EWaitingForData:
        	//Data from the Slave arrived
        	//The state machine can start listening again for incoming data   
        case ESendingData:
        	//Master sent data to the Slave successfully
        	//The state machine can start listening again for incoming data   
        	WaitForData();
            break;
        default:
			User::Leave(KErrCorrupt);
            break;
        };	
	}

/*
============================================================================
Sends a service search request to the remote device (Slave)
In our example we have decided to filter our service discovering only by service UUID. If your application
needs to make more complex filtering then you need to set the attribute's UUID you want to use in your search
by calling CSdpSearchPattern::AddL in CBluetoothServiceSearcher::FindServiceByUUIDL.
FindServiceByUUIDL is an asyncronus function. When the search is completed FindServiceByUUIDL calls
the callback function CBluetoothConnector::OnServiceSearchComplete either with the BT service's port
to connect to or with an error (i.e KErrNotFound)
============================================================================
*/
void CBluetoothConnector::SearchServiceL()
	{
	iState = ESearchingService;
	TUUID serviceUUID(KRPS_BTServiceID);
	iServiceSearcher->FindServiceByUUIDL(iBtDevAddr, serviceUUID);
	}

/*
============================================================================
Connects to the remote device (Slave) using a BT device address and the RFCOMM's port
After we have a valid BT device address and a valid BT service port we use CBluetoothSockConnector::ConnectToRemoteBtDeviceL
to connect to the remote BT device. CBluetoothSockConnector uses the BT socket layer. ConnectToRemoteBtDeviceL
is an Asynchronous function and  when the connection is completed it calls the callback
CBluetoothConnector::OnSockConnectionComplete with the connection error (KErrNone for a succesful connection)
============================================================================
*/		
void CBluetoothConnector::ConnectDeviceL()
	{
	iState = EConnecting;
	iSocketConnector->ConnectToRemoteBtDeviceL(iBtDevAddr, iPort);
	}

/*
============================================================================
Sets the state machine in the listening mode for remote device's incoming data.
Listening is done with CSocketReader (a separate active object). CSocketReader calls the callback
CBluetoothConnector::ReportData either with the data from the remote device or with a connection error.
============================================================================
*/		
void CBluetoothConnector::WaitForData()
	{
	if(!iBtSocketReader->IsActive() && iConnected)
		{			
		iState=EWaitingForData;
		iBtSocketReader->ReadData();
		}
	}

/*
============================================================================
Sends the data to the remote device (Slave). RunL will handle the sending data completion.
============================================================================
*/		
void CBluetoothConnector::SendData(const TDesC8& aData)
	{
	/*
	============================================================================
	If CSocketWriter is already active the data is discarded. RPS doesn't need to queue events.
	You can change CSocketWriter's implementation to support queuing of events if our multiplayer game needs to. 
	============================================================================
	*/		
	if(!iBtSocketWriter->IsActive() && iConnected)
		{			
		iState=ESendingData;
		iBtSocketWriter->Write(aData);
		}
	}

/*
============================================================================
If an error occurs at any stage of the state machine, cancel all the CBluetoothConnector's
active objects. Then the state machine is reset to the EIdle state
============================================================================
*/		
void CBluetoothConnector::CancelSocket()
	{
	iConnected = EFalse;
	if(iSocketConnector->IsActive())
		{		
		iSocketConnector->Cancel();
		}
	iBtSocketWriter->Cancel();
	iBtSocketReader->Cancel();
	iState = EIdle;
	}

/*
============================================================================
Callback from CBluetoothServiceSearcher.
If succesful aPort will hold the RFCOMM's port to connect to.
============================================================================
*/		
void CBluetoothConnector::OnServiceSearchComplete(TInt aPort, TInt aError)
	{
	if(aError == KErrNone)
		{
		//Remember the service's port and carry on with the state machine (connect to device)
		iMaxIterConnection = 0;
		iPort = aPort;
		SelfComplete();	
		}
	else
		{
		if(iMaxIterConnection < KMaxIterConnection)
			{					
			iMaxIterConnection++;
			User::After(TTimeIntervalMicroSeconds32(KOneSecondInMicroSeconds));
			SearchServiceL();
			}
		else
			{				
			//An error occured, cancel all active objects and report the error to the engine
			iMaxIterConnection = 0;
			CancelSocket();
			iConnObs.ConnectionErr(Handle(), aError);
			}
		}
	}

/*
============================================================================
Callback from CBluetoothSockConnector on connection completion
============================================================================
*/		
void CBluetoothConnector::OnSockConnectionComplete(TInt aError)
	{
	//report the error to the engine.
	if(aError == KErrNone)
		{
		iConnected = ETrue;
		iConnObs.ConnectionErr(Handle(), aError);
		iMaxIterConnection = 0;
		//carry on with the state machine (listen for incoming/sending data)
		SelfComplete();
		}
	else
		{
		if(iMaxIterConnection < KMaxIterConnection)
			{				
			iMaxIterConnection++;
			User::After(TTimeIntervalMicroSeconds32(KOneSecondInMicroSeconds));
			ConnectDeviceL();
			}
		else
			{
			iConnObs.ConnectionErr(Handle(), aError);
			iMaxIterConnection = 0;
			//cancel all active objects
			CancelSocket();
			}
		}
	}
	
/*
============================================================================
Callback from CSocketWriter on sending data completion
============================================================================
*/
void CBluetoothConnector::WriteComplete(TInt aError)
	{
	if(aError == KErrNone)
		{
		//carry on with the state machine (listen for incoming/sending data)
		iConnObs.SendDataComplete(Handle());
		SelfComplete();
		}
	else
		{			
		//An error occured, cancel all active objects and report the error to the engine
		CancelSocket();
		iConnObs.ConnectionErr(iHandle, aError);	
		}
	}

/*
============================================================================
Callback from CSocketReader on receiving data completion
============================================================================
*/
void CBluetoothConnector::ReportData(const TDesC8& aData, TInt aError)
	{		
	if(aError == KErrNone)
		{
		//Pass the data to the engine and carry on with the state machine (listen for incoming/sending data)
		iConnObs.DataReceived(iHandle, aData);
		SelfComplete();
		}
	else
		{			
		//An error occured, cancel all active objects and report the error to the engine
		CancelSocket();
		iConnObs.ConnectionErr(iHandle, aError);	
		}
	}

/*
============================================================================
Call User::RequestComplete on this active object. Used to trigger the next step of
the CBluetoothConnector's state machine.
============================================================================
*/
void CBluetoothConnector::SelfComplete()
	{
	TRequestStatus* status = &iStatus;
	User::RequestComplete(status, KErrNone);
	SetActive();			
	}

/*
============================================================================
Handles a leave occurring in the request completion event handler RunL().
Close all active BT sockets, reset the state machine to the EIdle state and
report the error to the observer 
============================================================================
*/
TInt CBluetoothConnector::RunError(TInt aError)
	{
	CancelSocket();
	iConnObs.ConnectionErr(iHandle, aError);
	return KErrNone;
	}

